package top.hekun.study.asm;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;

import static org.objectweb.asm.Opcodes.*;

/**
 * 〈方法访问器〉
 *
 * @author: Sakura
 * @date: 2023/4/22
 */
public class MyMethodVisitor extends LocalVariablesSorter {
    // 方法开始的时间戳
    private int slotIndex;
    MyMethodVisitor(int apiVersion, int access,  String descriptor,MethodVisitor methodVisitor) {
        /**
         * api–此访问者实现的ASM api版本。必须是操作代码中的ASMx值之一。
         * access–自适应方法的访问标志。
         * descriptor–方法的描述符（请参见类型）。
         * methodVisitor–此适配器委托调用的方法访问者。
         */
        super(apiVersion, access, descriptor, methodVisitor);
    }

    @Override
    public void visitCode() {

        // 准备一个 long 类型
        Type longType = Type.LONG_TYPE;
        // 首先，获取一个变量空间 地址
        slotIndex = newLocal(longType);
        // 调用静态方法 获取时间戳
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        // 存储时间戳到 指定位置
        mv.visitVarInsn(longType.getOpcode(ISTORE), slotIndex);
        super.visitCode();

    }


    @Override
    public void visitInsn(int opcode) {

        // 在调用返回前 进行处理 正常返回
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {

            // 准备一个 long 类型
            Type longType = Type.LONG_TYPE;
            //  首先，获取一个变量空间 地址
            int endIndex = newLocal(longType);
            // 调用静态方法 获取时间戳
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            // 存储时间戳到 指定位置
            mv.visitVarInsn(longType.getOpcode(ISTORE), endIndex);
            // 加载 变量
            mv.visitVarInsn(longType.getOpcode(ILOAD), endIndex);
            mv.visitVarInsn(longType.getOpcode(ILOAD), slotIndex);
            // 执行减法
            mv.visitInsn(longType.getOpcode(ISUB));
            int returnIndex = newLocal(longType);
            // 结果缓存
            mv.visitVarInsn(longType.getOpcode(ISTORE), returnIndex);
            //   准备字符串拼接,准备一个StringBuilder
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv.visitInsn(DUP);
            // 调用初始化方法 会消耗 一个栈顶的引用
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            // 加载字符串
            mv.visitLdcInsn("方法执行时间为: ");
            // 调用append方法, 消耗栈顶2个数据, 并返回一个数据
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            // 加载变量  这步和 上面的 结果缓存 可以一起省略
            mv.visitVarInsn(longType.getOpcode(ILOAD), returnIndex);
            // 将 缓存的时间查加载到
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", Type.getMethodDescriptor(Type.getType("Ljava/lang/StringBuilder;"), longType), false);
            // 加载字符串
            mv.visitLdcInsn(" 毫秒.");
            // 调用append方法, 消耗栈顶2个数据, 并返回一个数据
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            // 调用 toString 消耗栈顶数据,并返回字符串
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            // 加载 System的静态变量 out, 类型是 PrintStream
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            // 执行交换
            mv.visitInsn(SWAP);
            // 调用println 消耗 字符串 和加载的PrintStream
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }

        super.visitInsn(opcode);
    }


}
